/* Copyright (c) 2015,2016 Lucky Byte, Inc.
 */
package com.lucky_byte.pay.jar;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLTimeoutException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.postgresql.ds.PGPoolingDataSource;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

/**
 * 通过 JDBC 访问数据库
 *
 * 这个类提供的都是静态方法，因此不需要建立这个类的对象
 */
public class Jdbc
{
	public static final int DBDIR_INVALID = -1;
	public static final int DBDRI_PGSQL = 1;

	private static final Logger logger = LogManager.getLogger();
	private static DataSource data_source = null;
	private static String driver = null;

	// 设置 PostgreSQL JDBC 数据源
	static private DataSource getPgDataSource(JsonObject config)
			throws SQLException {
		PGPoolingDataSource pgsql_ds = new PGPoolingDataSource();

		pgsql_ds.setDataSourceName("data_source");
		pgsql_ds.setServerName(config.get("server_name").getAsString());
		pgsql_ds.setPortNumber(config.get("server_port").getAsInt());
		pgsql_ds.setDatabaseName(config.get("db_name").getAsString());
		pgsql_ds.setUser(config.get("user").getAsString());
		pgsql_ds.setPassword(config.get("passwd").getAsString());
		pgsql_ds.setInitialConnections(config.get("init_conns").getAsInt());
		pgsql_ds.setMaxConnections(config.get("max_conns").getAsInt());
		pgsql_ds.setConnectTimeout(config.get("timeout").getAsInt());
		pgsql_ds.setLoginTimeout(15);
		pgsql_ds.setTcpKeepAlive(true);
		pgsql_ds.setApplicationName(Version.APPNAME);
		pgsql_ds.setProperty("stringtype", "unspecified");

		return pgsql_ds;
	}

	static private void loadDriver(String driver, JsonObject params)
			throws SQLException {
		switch(driver) {
		case "pgsql":
			data_source = getPgDataSource(params);
			break;
		default:
			logger.fatal("[{}]不是有效的数据库驱动.", driver);
			break;
		}
	}

	// 加载数据库配置
	static private void loadConfig() {
		if (data_source != null) {
			return;
		}
		File file = Runtime.fileFor("/etc/jdbc.json");
		if (file == null || !file.exists()) {
			logger.fatal("未找到数据库配置文件[jdbc.json].");
			return;
		}
		try {
			JsonObject jdbc_json = new JsonParser().parse(
					new InputStreamReader(
							new FileInputStream(file),"utf-8")).getAsJsonObject();
			JsonElement object = jdbc_json.get("driver");
			if (object == null || !object.isJsonPrimitive()) {
				logger.fatal("数据库配置中未找到有效的驱动，请检查...");
				return;
			}
			driver = object.getAsString();
			JsonElement config = jdbc_json.get(driver);
			if (config == null) {
				logger.error("数据库配置中未找到[{}]的配置，请检查...", driver);
				return;
			}
			loadDriver(driver, config.getAsJsonObject());
		} catch (Exception e1) {
			logger.fatal("数据库配置文件[jdbc.json]格式错误.");
		}
	}

	/**
	 * 获取当前数据库驱动名
	 *
	 * @return 数据库驱动名
	 */
	static public int getDBDriver() {
		if (data_source == null) {
			loadConfig();
		}
		if (driver != null) {
			switch (driver) {
			case "pgsql":
				return DBDRI_PGSQL;
			}
		}
		return DBDIR_INVALID;
	}

	/**
	 * 取数据库连接
	 *
	 * @return JDBC 连接
	 * @throws java.sql.SQLException SQL 异常
	 */
	static public Connection getConnection() throws SQLException {
		if (data_source == null) {
			loadConfig();
		}
		if (data_source == null) {
			throw new SQLException("数据库尚未连接，不能执行操作");
		}
		try {
			return data_source.getConnection();
		} catch (SQLTimeoutException e) {
			logger.info("数据库连接超时，重新连接...");
			loadConfig();
			return data_source.getConnection();
		}
	}

	/**
	 * 记录 SQL 语句执行时间
	 *
	 * @param start 起始时间
	 * @param statement 执行语句
	 */
	public static void logElapsed(long start, Statement statement) {
		long elapsed = System.currentTimeMillis() - start;
		logger.debug("执行SQL[{}]耗费[{}]秒.", statement.toString(),
				(double) (elapsed) / 1000);
	}

	/**
	 * 记录 SQL 语句执行时间
	 *
	 * @param start 起始时间
	 * @param statement SQL 字符串
	 */
	public static void logElapsed(long start, String sql) {
		long elapsed = System.currentTimeMillis() - start;
		logger.debug("执行SQL[{}]耗费[{}]秒.", sql, (double) (elapsed) / 1000);
	}

	/**
	 * 执行查询操作，不要使用此函数执行 <b>UPDATE/INSERT/DELETE</b>
	 *
	 * @param sql SQL 语句
	 * @param params 参数对象，用于补充 sql 语句中的 ?
	 * @param listener 查询结果回调接口
	 * @throws SQLException SQL 异常
	 */
	static public void query(String sql, List<Object> params,
			QueryListener listener) throws SQLException {
		if (listener == null) { // 查询操作如果没有监听回调，则不需要作任何操作
			return;
		}
		long start = System.currentTimeMillis();

		Connection connection = getConnection();
		connection.setAutoCommit(true);
		PreparedStatement statement = null;
		try {
			// 查询结果支持更新操作
			statement = connection.prepareStatement(sql);
			if (params != null) {
				for (int i = 0; i < params.size(); i++) {
					statement.setObject(i + 1, params.get(i));
				}
			}
			logger.trace("开始执行SQL[{}].", statement.toString());

			statement.setQueryTimeout(30);
			ResultSet rset = statement.executeQuery();
			logElapsed(start, rset.getStatement());

			listener.onQuery(rset);
			if (!rset.isClosed()) {
				rset.close();
			}
			statement.close();
			connection.close();
		} catch (SQLException e) {
			if (statement != null) {
				statement.close();
			}
			connection.close();
			throw e;
		}
	}

	/**
	 * 执行查询操作，不要使用此函数执行 <b>UPDATE/INSERT/DELETE</b>
	 *
	 * @param sql SQL 语句
	 * @param params 参数对象，用于补充 sql 语句中的 ?
	 * @param listener 查询结果回调接口
	 * @throws SQLException SQL 异常
	 */
	static public void query(String sql, Object[] params,
			QueryListener listener) throws SQLException {
		List<Object> list = new ArrayList<>();
		for (Object obj : params) {
			list.add(obj);
		}
		query(sql, list, listener);
	}

	/**
	 * 执行查询操作，不要使用此函数执行 UPDATE/INSERT/DELETE
	 *
	 * @param sql SQL 语句
	 * @param listener 查询结果回调接口
	 * @throws SQLException SQL 异常
	 */
	static public void query(String sql, QueryListener listener)
			throws SQLException {
		query(sql, (Object[])null, listener);
	}

	/**
	 * 执行单个 UPDATE/INSERT/DELETE 操作
	 *
	 * @param sql SQL 语句
	 * @param params 参数对象，用于补充 sql 语句中的 ?
	 * @param listener 更新结果回调接口
	 * @throws SQLException SQL 异常
	 */
	static public void update(String sql, List<Object> params,
			UpdateListener listener) throws SQLException {
		long start = System.currentTimeMillis();

		Connection connection = getConnection();
		connection.setAutoCommit(true);
		PreparedStatement statement = null;
		try {
			statement = connection.prepareStatement(sql);
			if (params != null) {
				for (int i = 0; i < params.size(); i++) {
					statement.setObject(i + 1, params.get(i));
				}
			}
			logger.trace("开始执行SQL[{}].", statement.toString());

			statement.setQueryTimeout(30);
			int count = statement.executeUpdate();
			logElapsed(start, statement);

			if (listener != null && listener instanceof UpdateListener) {
				listener.onUpdate(count);
			}
			statement.close();
			connection.close();
		} catch (SQLException e) {
			if (statement != null) {
				statement.close();
			}
			connection.close();
			throw e;
		}
	}

	/**
	 * 执行 UPDATE/INSERT/DELETE 操作
	 *
	 * @param sql SQL 语句
	 * @param listener 更新结果回调接口
	 * @throws SQLException SQL 异常
	 */
	static public void update(String sql, UpdateListener listener)
			throws SQLException {
		update(sql, null, listener);
	}

	/**
	 * 执行 UPDATE/INSERT/DELETE 操作
	 *
	 * @param sql SQL 语句
	 * @param params 参数对象，用于补充 sql 语句中的 ?
	 * @throws SQLException SQL 异常
	 */
	static public void update(String sql, List<Object> params)
			throws SQLException {
		update(sql, params, null);
	}

	/**
	 * 执行 UPDATE/INSERT/DELETE 操作，不需要回调处理
	 *
	 * @param sql SQL 语句
	 * @throws SQLException SQL 异常
	 */
	static public void update(String sql) throws SQLException {
		update(sql, null, null);
	}

	/**
	 * 开始事务
	 *
	 * @throws SQLException SQL 异常
	 */
	static public void begin() throws SQLException {
		update("BEGIN");
	}

	/**
	 * 提交事务
	 *
	 * @throws SQLException SQL 异常
	 */
	static public void commit() throws SQLException {
		update("COMMIT");
	}

	/**
	 * 回滚事务
	 *
	 * @throws SQLException SQL 异常
	 */
	static public void rollback() throws SQLException {
		update("ROLLBACK");
	}

	/**
	 * 查询回调接口
	 */
	public interface QueryListener {
		void onQuery(ResultSet rset) throws SQLException;
	}

	/**
	 * 更新回调接口
	 */
	public interface UpdateListener {
		void onUpdate(int count);
	}

}
